﻿module main;

import std.exception;
import std.stdio;
import std.regex;
import std.conv;
import std.algorithm;
import std.range;
import std.string;

enum beginCtr = ctRegex!("^[ 0-9]{4,4}[0-9].*");
unittest
{
    assert( match("    1", beginCtr));
    assert( match("   11", beginCtr));
    assert( match("  111", beginCtr));
    assert( match(" 1111", beginCtr));
    assert( match("11111", beginCtr));
    assert(!match("     ", beginCtr));
    assert(!match("    a", beginCtr));
    assert(!match("sdsad", beginCtr));
    assert(!match("as112", beginCtr));
}

void showOpt()
{
    writeln("FontUtils");
    writeln("supported options:");
    writeln("join file1 [file2...]");
    writeln("genarray fontfile mapfile");
}

struct Point
{
    int x, y;
}

struct Glyph
{
    uint index;
    int minX, maxX;
    int minY, maxY;
    Point[] points;
}

auto parseGlyphString(in char[] str) pure
{
    Glyph ret;
    ret.index      = to!uint(strip(str[0..5]));
    auto vertcount = to!uint(strip(str[5..8]));
    enforce(vertcount > 0, "Invalid vertices count");
    ret.minX = str[8] - 'R';
    ret.maxX = str[9] - 'R';
    ret.minY = 0;
    ret.maxY = 0;
    foreach(i;0..vertcount - 1)
    {
        auto ind = 10 + i * 2;
        auto ch = str[ind..ind + 2];
        Point pt = {int.min,int.min};
        if(" R" != ch)
        {
            pt.x = ch[0] - 'R';
            pt.y = ch[1] - 'R';
            ret.minY = min(ret.minY, pt.y);
            ret.maxY = max(ret.maxY, pt.y);
        }
        ret.points ~= pt;
    }
    return ret;
}

void main(string[] args)
{
    if(args.length < 2)
    {
        showOpt();
    }
    if("join" == args[1])
    {
        enforce(args.length > 2, "filenames required");

        char[] buf;
        int i = 0;
        foreach(fname;args[2..$])
        {
            auto f = File(fname, "r");
            while(f.readln(buf))
            {
                while(buf.length > 0 && (10 == buf[$ - 1] || 13 == buf[$ - 1]))
                {
                    --buf.length;
                }

                if((0 != i) && match(buf, beginCtr))
                {
                    writeln();
                }

                write(buf);
                ++i;
            }
        }
    }
    else if("genarray" == args[1])
    {
        enforce(args.length > 3, "filenames required");
        char[] buf;
        auto fontname = args[2];
        auto mapname = args[3];
        Glyph[uint] glyphs;

        {
            auto f = File(fontname, "r");
            while(f.readln(buf))
            {
                if(!match(buf, beginCtr)) continue;
                auto g = parseGlyphString(buf);
                glyphs[g.index] = g;
            }
        }

        uint[] indices;
        dchar[] chars;

        int baseline = 0;
        int capline = 0;

        {
            int i = 0;
            auto f = File(mapname, "r");
            while(f.readln(buf))
            {
                auto rng = splitter(buf);
                if(rng.empty || '#' == rng.front[0]) continue;
                if(0 == i)
                {
                    baseline = to!int(rng.front);
                    rng.popFront();
                    enforce(!rng.empty, "No value");
                    capline  = to!int(rng.front);
                }
                else
                {
                    dchar ch    = to!uint(rng.front);
                    rng.popFront();
                    enforce(!rng.empty, "No value");
                    auto index = to!uint(rng.front);
                    indices ~= index;
                    chars   ~= ch;
                }
                ++i;
            }
        }
        assert(indices.length == chars.length);
        enforce(baseline > capline, "Invalid capline or baseline: "~text(baseline)~" "~text(capline));

        struct VecGlyph
        {
            dchar ch;
            int width;
            int height;
            int base;
            size_t dataOffset;
            size_t numPoints;
        }

        int[] glyphsData;
        VecGlyph[] glyphsDesc;

        foreach(i,ind;indices)
        {
            auto ch = chars[i];
            auto g = enforce(ind in glyphs, "Glyph not found: "~text(ind)~" "~text(ch));
            auto offset = glyphsData.length;
            foreach(pt;g.points)
            {
                int x = -1;
                int y = -1;
                if(pt.x != int.min)
                {
                    enforce(pt.x >= g.minX  && pt.x <= g.maxX,   "Invalid point X: "~text(pt.x));
                    //enforce(pt.y >= capline && pt.y <= baseline, "Invalid point Y: "~text(pt.y));
                    x = pt.x - g.minX;
                    y = pt.y - g.minY;
                }
                glyphsData ~= x;
                glyphsData ~= y;
            }

            VecGlyph gd;
            gd.ch = ch;
            gd.width  = g.maxX - g.minX;
            gd.height = g.maxY - g.minY;
            gd.base = baseline - g.minY;
            gd.dataOffset = offset;
            gd.numPoints = g.points.length;
            glyphsDesc ~= gd;
        }

        write("module "~mapname~`;
// This file is autogenerated
enum FontHeight = `~text(baseline - capline)~`;
private struct GlyphDesc
{
    dchar ch;
    int width;
    int height;
    int base;
    size_t dataOffset;
    size_t numPoints;
}
enum GlyphDesc[] GlyphsDesc = [`);
        foreach(i,g;glyphsDesc)
        {
            if(0 != i) write(",");
            writeln();
            write(format("{ch:%4d, width:%3d, height:%3d, base:%2d, dataOffset:%5d, numPoints:%3d}",
                         g.ch,   g.width,   g.height,   g.base,   g.dataOffset,   g.numPoints));
        }
        write("];\nenum short[] GlyphsData = [");
        foreach(i,d;glyphsData)
        {
            if(0 != i) write(",");
            if(0 == (i % 30)) writeln();
            writef("%3d",d);
        }
        write("];");


    }
    else
    {
        showOpt();
    }
}
